package com.iwuyc.fastexcel.ext.core

import cn.idev.excel.EasyExcel
import cn.idev.excel.context.AnalysisContext
import cn.idev.excel.metadata.CellExtra
import cn.idev.excel.metadata.data.ReadCellData
import cn.idev.excel.read.builder.ExcelReaderBuilder
import cn.idev.excel.read.listener.ReadListener
import cn.idev.excel.support.ExcelTypeEnum
import com.google.common.io.Files
import com.iwuyc.fastexcel.ext.data.CellInfo
import com.iwuyc.fastexcel.ext.data.ExcelRowInfo
import com.iwuyc.fastexcel.ext.data.IndexInfo
import com.iwuyc.fastexcel.ext.data.SheetInfo
import com.iwuyc.fastexcel.ext.interfaces.RowConsumer
import org.slf4j.LoggerFactory
import java.io.File

/**
 * excel 解析器,thread unsafe
 *
 * @param file 待解析的excel文件
 * @author 吴宇春
 * @date 2022/12/28
 * @version 1.0.0
 */
class ExcelParser(file: File) {
    /**
     * 文件类型
     */
    private val type: ExcelTypeEnum

    /**
     * 待解析的文件
     */
    private val file: File

    /**
     * Excel读取器
     */
    private val excelReader: ExcelReaderBuilder

    /**
     * 默认行消费者
     */
    private var defaultRowConsumer: RowConsumer? = null

    init {
        val fileExtension = Files.getFileExtension(file.name)
        type = if ("xls".equals(fileExtension, ignoreCase = true)) {
            ExcelTypeEnum.XLS
        } else if ("xlsx".equals(fileExtension, ignoreCase = true)) {
            ExcelTypeEnum.XLSX
        } else if ("csv".equals(fileExtension, ignoreCase = true)) {
            ExcelTypeEnum.CSV
        } else {
            throw IllegalArgumentException("不支持的文件类型")
        }
        this.file = file
        this.excelReader = EasyExcel.read(file)
    }

    /**
     * 解析Excel文件
     * @param rowConsumer 行消费者
     * @param headRowNum 标题所在行号，从1开始
     * @param sheetName sheet的名称，未提供则默认选第一个
     * @param sheetNo sheet的编号，从0开始
     * @param autoTrim 是否自动去除包括sheet名称以及content数据的前后空白字符。默认为true
     */
    fun parse(
        rowConsumer: RowConsumer? = null,
        headRowNum: Int = 1,
        sheetName: String? = null,
        sheetNo: Int = 0,
        autoTrim: Boolean = true
    ): List<ExcelRowInfo> {
        // 创建一个Excel读取监听器
        val rowConsumer = rowConsumer ?: this.defaultRowConsumer
        val readerListener = ExcelReaderListener(rowConsumer)
        this.excelReader.registerReadListener(readerListener)

        this.excelReader.excelType(type)
        if (sheetName != null) {
            this.excelReader.sheet(sheetName)
        } else {
            this.excelReader.sheet(sheetNo)
        }
        this.excelReader.autoTrim(autoTrim)
        this.excelReader.headRowNumber(headRowNum)
        this.excelReader.sheet().doRead()
        return readerListener.rowList
    }

    /**
     * 解析整个Excel文件，每个sheet都共用一个行消费者
     * @param rowConsumer 行消费者
     * @param headRowNum 标题所在行号，从1开始
     * @param autoTrim 是否自动去除包括sheet名称以及content数据的前后空白字符。默认为true
     */
    fun parseAll(
        rowConsumer: RowConsumer? = null,
        headRowNum: Int = 1,
        autoTrim: Boolean = true
    ): List<ExcelRowInfo> {
        // 创建一个Excel读取监听器
        val rowConsumer = rowConsumer ?: this.defaultRowConsumer
        val readerListener = ExcelReaderListener(rowConsumer)
        this.excelReader.registerReadListener(readerListener)
        this.excelReader.excelType(type)
        this.excelReader.autoTrim(autoTrim)
        this.excelReader.headRowNumber(headRowNum)
        this.excelReader.doReadAll()
        return readerListener.rowList
    }

    /**
     * 注册默认行消费者
     * @param rowConsumer 行消费者
     */
    fun registerDefaultRowConsumer(rowConsumer: RowConsumer) {
        // 创建一个Excel读取监听器
        this.defaultRowConsumer = rowConsumer
    }
}

/**
 * Excel读取监听器
 * @param rowConsumer 行消费者
 */
class ExcelReaderListener(var rowConsumer: RowConsumer?) : ReadListener<Map<Int, String>> {
    private val sheepInfoBySheetNo: MutableMap<Int, SheetInfo> = HashMap()
    var rowList: MutableList<ExcelRowInfo> = ArrayList()

    override fun onException(exception: Exception?, context: AnalysisContext?) {
        exception?.printStackTrace()
    }

    override fun invokeHead(headMap: MutableMap<Int, ReadCellData<*>>?, context: AnalysisContext?) {
        if (null == headMap) {
            return
        }
        val sheetInfo = getSheetInfo(context)
        val sheetHeaderMap = sheetInfo.headMap
        sheetHeaderMap += headMap
    }

    private fun getSheetInfo(context: AnalysisContext?): SheetInfo {
        val readSheetHolder = context!!.readSheetHolder()
        val sheetNo = readSheetHolder.sheetNo
        val sheetInfo = sheepInfoBySheetNo.computeIfAbsent(sheetNo) {
            SheetInfo(
                sheetNo = sheetNo,
                sheetName = readSheetHolder.sheetName
            )
        }
        return sheetInfo
    }

    override fun extra(extra: CellExtra?, context: AnalysisContext?) {
        println("extra:${extra}")
    }

    override fun doAfterAllAnalysed(context: AnalysisContext?) {
        println("doAfterAllAnalysed")
    }

    override fun hasNext(context: AnalysisContext?): Boolean {
        return null != context
    }

    override fun invoke(dataByIndex: Map<Int, String>?, context: AnalysisContext?) {
        try {
            if (null == context || dataByIndex == null) {
                return
            }
            val readRowHolder = context.readRowHolder()
            val rowIndex = readRowHolder.rowIndex
            val cellInfos: MutableMap<String, CellInfo> = HashMap()

            val sheetInfo = getSheetInfo(context)

            val headMap = sheetInfo.headMap
            for ((columnIndex, cellData) in dataByIndex.entries) {
                val headInfo = headMap[columnIndex]
                val columnName = headInfo?.stringValue ?: "$columnIndex"

                val indexInfo = IndexInfo(rowIndex = rowIndex, columnIndex = columnIndex)
                val cellInfo = CellInfo(columnName, indexInfo, cellData)

                cellInfos += columnName to cellInfo
            }

            val excelRowInfo = ExcelRowInfo(sheetInfo = sheetInfo, rowIndex = rowIndex, cellInfos = cellInfos)
            this.rowList += excelRowInfo
            if (null != this.rowConsumer) {
                this.rowConsumer!!.consumer(excelRowInfo)
            }
        } catch (e: Exception) {
            log.error("解析Excel文件异常", e)
        }
    }

    private val log = LoggerFactory.getLogger(ExcelReaderListener::class.java)
}